﻿using System;
using UnityEngine;
using System.Text;

namespace Miao.ML
{
    internal static class WinMLForUnity
    {
        private static readonly StringBuilder stringBuilder = new StringBuilder(24);

        public static void AddToSerializer(WinMLSerializer winMLSerializer)
        {
            winMLSerializer.AddSerializationFunction<Rect>(RectSerializationFunction);
            winMLSerializer.AddSerializationFunction<Vector2Int>(Vector2IntSerializationFunction);
            winMLSerializer.AddSerializationFunction<AnimationCurve>(AnimationCurveSerializationFunction);
        }
        public static void AddToDeserializer(WinMLDeserializer winMLDeserializer)
        {
            winMLDeserializer.AddDeserializationFunction(RectDeserializationFunction);
            winMLDeserializer.AddDeserializationFunction(Vector2IntDeserializationFunction);
            winMLDeserializer.AddDeserializationFunction(AnimationCurveDeserializationFunction);
        }

        public static string Vector2IntSerializationFunction(Vector2Int vector2Int)
        {
            stringBuilder.Length = 0;

            stringBuilder.Append(vector2Int.x);
            stringBuilder.Append(',');
            stringBuilder.Append(vector2Int.y);

            return stringBuilder.ToString();
        }
        public static Vector2Int Vector2IntDeserializationFunction(string text)
        {
            SplitOnce(text.AsSpan(), ',', out ReadOnlySpan<char> x, out ReadOnlySpan<char> y);
            return new Vector2Int(int.Parse(x), int.Parse(y));
        }

        public static string RectSerializationFunction(Rect rect)
        {
            stringBuilder.Length = 0;

            stringBuilder.Append(rect.x);
            stringBuilder.Append(',');
            stringBuilder.Append(rect.y);
            stringBuilder.Append(',');
            stringBuilder.Append(rect.width);
            stringBuilder.Append(',');
            stringBuilder.Append(rect.height);

            return stringBuilder.ToString();
        }
        public static Rect RectDeserializationFunction(string text)
        {
            SplitOnce(text.AsSpan(), ',', out ReadOnlySpan<char> x, out ReadOnlySpan<char> other);
            SplitOnce(other, ',', out ReadOnlySpan<char> y, out other);
            SplitOnce(other, ',', out ReadOnlySpan<char> width, out ReadOnlySpan<char> height);

            return new Rect(float.Parse(x), float.Parse(y), float.Parse(width), float.Parse(height));
        }

        public static string AnimationCurveSerializationFunction(AnimationCurve animationCurve)
        {
            stringBuilder.Length = 0;

            Keyframe[] keyframes = animationCurve.keys;
            int length = keyframes.Length;
            if (length > 0)
            {
                for (int i = 0; i < length; i++)
                {
                    Keyframe keyframe = keyframes[i];
                    stringBuilder.Append(keyframe.time);
                    stringBuilder.Append(',');
                    stringBuilder.Append(keyframe.value);
                    stringBuilder.Append(',');
                    stringBuilder.Append(keyframe.inTangent);
                    stringBuilder.Append(',');
                    stringBuilder.Append(keyframe.outTangent);
                    stringBuilder.Append(',');
                    stringBuilder.Append(keyframe.inWeight);
                    stringBuilder.Append(',');
                    stringBuilder.Append(keyframe.outWeight);
                    stringBuilder.Append(',');
                    stringBuilder.Append(keyframe.weightedMode);
                    stringBuilder.Append('|');
                }
                stringBuilder.Length -= 1;
            }

            return stringBuilder.ToString();
        }
        public static AnimationCurve AnimationCurveDeserializationFunction(string text)
        {
            if (text == null) return null;
            AnimationCurve animationCurve = new AnimationCurve();
            if (text.Length == 0) return animationCurve;

            ReadOnlySpan<char> currentSpan = text.AsSpan();
            while (SplitOnceReuse(currentSpan, '|', out ReadOnlySpan<char> result, ref currentSpan)) AddKey(result);
            AddKey(currentSpan);

            void AddKey(ReadOnlySpan<char> data)
            {
                SplitOnce(data, ',', out ReadOnlySpan<char> time, out data);
                SplitOnce(data, ',', out ReadOnlySpan<char> value, out data);
                SplitOnce(data, ',', out ReadOnlySpan<char> inTangent, out data);
                SplitOnce(data, ',', out ReadOnlySpan<char> outTangent, out data);
                SplitOnce(data, ',', out ReadOnlySpan<char> inWeight, out data);
                SplitOnce(data, ',', out ReadOnlySpan<char> outWeight, out ReadOnlySpan<char> weightedMode);

                Keyframe keyframe = new Keyframe(float.Parse(time), float.Parse(value), float.Parse(inTangent), float.Parse(outTangent), float.Parse(inWeight), float.Parse(outWeight));
                keyframe.weightedMode = Enum.Parse<WeightedMode>(weightedMode.ToString());
                animationCurve.AddKey(keyframe);
            }

            return animationCurve;
        }

        private static bool SplitOnceReuse(ReadOnlySpan<char> textSpan, char separator, out ReadOnlySpan<char> one, ref ReadOnlySpan<char> two)
        {
            for (int i = 0; i < textSpan.Length; i++)
            {
                if (textSpan[i] == separator)
                {
                    one = textSpan[..i];
                    two = textSpan[(i + 1)..];
                    return true;
                }
            }
            one = default;
            return false;
        }
        private static bool SplitOnce(ReadOnlySpan<char> textSpan, char separator, out ReadOnlySpan<char> one, out ReadOnlySpan<char> two)
        {
            for (int i = 0; i < textSpan.Length; i++)
            {
                if (textSpan[i] == separator)
                {
                    one = textSpan[..i];
                    two = textSpan[(i + 1)..];
                    return true;
                }
            }
            one = two = default;
            return false;
        }
    }
}